package com.jinyang.jetpackdemo.util.gson

import com.google.gson.Gson
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaField

/**
 * @Author:         LiuJinYang
 * @CreateDate:     2021/12/1
 * @Description:
 */
class KotlinAdapterFactory : TypeAdapterFactory {
    override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
        val kClass = (type.rawType as Class<T>).kotlin
        val delegateAdapter = gson.getDelegateAdapter(this, type)
        return object : TypeAdapter<T>() {
            override fun write(writer: JsonWriter, value: T) {
                delegateAdapter.write(writer, value)
            }

            override fun read(reader: JsonReader): T? {
                return delegateAdapter.read(reader)?.apply {
                    kClass.declaredMemberProperties.forEach { prop ->
                        prop.isAccessible = true
                        if (!prop.returnType.isMarkedNullable && prop(this) == null) {
                            when (val kClassType = prop.returnType.classifier as KClass<*>) {
                                Int::class, Short::class -> {
                                    prop.javaField?.set(this, 0)
                                }
                                Short::class -> {
                                    prop.javaField?.set(this, 0.toShort())
                                }
                                Long::class -> {
                                    prop.javaField?.set(this, 0L)
                                }
                                Char::class -> {
                                    prop.javaField?.set(this, '\u0000')
                                }
                                Boolean::class -> {
                                    prop.javaField?.set(this, false)
                                }
                                Byte::class -> {
                                    prop.javaField?.set(this, 0.toByte())
                                }
                                Float::class -> {
                                    prop.javaField?.set(this, .0F)
                                }
                                Double::class -> {
                                    prop.javaField?.set(this, .0)
                                }
                                String::class -> {
                                    prop.javaField?.set(this, "")
                                }
                                List::class -> {
                                    prop.javaField?.set(this, ArrayList<Any>())
                                }
                                else -> {
                                    prop.javaField?.set(this, kClassType.createInstance())
                                }
                            }
                        }
                    }
                } ?: kClass.createInstance()
            }
        }
    }
}